import config from "../utils/config"
import {IMessageEmitter, IMessageGroupEmitter, IMessageSource, Message} from "./messageBus/index"


type playerRankInfo = {
  _id: string,
  score: number,
  knockOut?: boolean
  lastRound?: number
}


export type TournamentConfig = {
  _id: string,
  gameType: string,
  nPlayersToEnd: number, 
  nPlayersToKnockOut: number,
  entryFee: number,
  playerCounter: number, 
  juShu: number, 
  rule: any
  queueLimit: number
}

export class Tournament {
  readonly _id: string
  private inPlayers: playerRankInfo[]
  private config: TournamentConfig
  private currentPlayerCount: number
  private nPlayersToEnd: number
  private nPlayersToKnockOut: number
  private ranks: playerRankInfo[]
  readonly messageSource: IMessageSource
  private broadcaster: IMessageGroupEmitter
  private currentRooms: { _id: string, players: playerRankInfo[], state: 'onGoing' | 'finish' }[]
  private currentRound: number
  private roomReporter: IMessageEmitter


  static tournamentInRollKey(_id: string) {
    return `TourEnRoll.${_id}`
  }

  constructor(tc: TournamentConfig, players: playerRankInfo[], messageBus: {
    source: IMessageSource
    emitter: IMessageGroupEmitter
    roomReport: IMessageEmitter
  }, readonly lobby: { startTournamentRoom(player, tc: TournamentConfig, report: IMessageEmitter): Promise<string> }) {
    this.config = tc
    this.nPlayersToEnd = 4
    this.nPlayersToKnockOut = this.config.nPlayersToKnockOut || 4
    this.inPlayers = players
    this.ranks = players.slice()
    this._id = this.config._id
    this.currentPlayerCount = this.inPlayers.length
    this.messageSource = messageBus.source
    this.broadcaster = messageBus.emitter
    this.roomReporter = messageBus.roomReport
    this.currentRound = 1
  }

  rank() {
    return this.ranks.sort((p1, p2) => {
      if (p1.lastRound === p2.lastRound) {
        return p2.score - p1.score
      } else {
        return p2.lastRound - p1.lastRound
      }
    })
  }

  private toGroups() {

    const groups: playerRankInfo[][] = []

    let currentGroup = []

    for (const p of this.inPlayers) {
      currentGroup.push(p)

      if (currentGroup.length === this.config.rule.playerCounter) {
        groups.push(currentGroup)
        currentGroup = []
      }
    }

    return groups
  }

  async startCurrentRound() {
    this.currentRooms = []
    for (const g of this.toGroups()) {
      g.forEach(p => p.lastRound = this.currentRound)

      const roomId = await this.lobby.startTournamentRoom(g, this.config, this.roomReporter)
      this.currentRooms.push({_id: roomId, players: g, state: 'onGoing'})
    }

    this.currentRound += 1
  }


  finishRoom(roomId: string) {
    const room = this.currentRooms.find(r => r._id === roomId)
    if (room) room.state = 'finish'
  }

  isCurrentRoundOver(): boolean {
    return this.currentRooms.every(r => r.state === 'finish')
  }

  private playersInCurrentRound(): number {
    return this.inPlayers.length
  }

  private isTournamentOver(): boolean {
    return this.isCurrentRoundOver() && this.playersInCurrentRound() === this.config.nPlayersToEnd
  }


  private currentRoundRank() {
    return this.inPlayers.sort((p1, p2) => p2.score - p1.score)
  }


  private knockOut() {
    const playerRank = this.currentRoundRank()

    const lastIndex = playerRank.length - 1

    const knockOutIds = []
    for (let i = 0; i < this.config.nPlayersToKnockOut; i++) {
      playerRank[lastIndex - i].knockOut = true

      knockOutIds.push(playerRank[lastIndex - i]._id)
    }

    this.broadcaster.emit({
      name: 'tournament/knockout', payload: {
        players: playerRank.filter(p => p.knockOut)
      }
    }, knockOutIds)

    this.inPlayers = playerRank.slice(0, playerRank.length - this.config.nPlayersToKnockOut)
  }


  private broadcastToInTournaPlayer(message: Message) {
    this.broadcaster.emit(message, this.inPlayers.map(p => p._id))
  }

  async start() {

    await this.messageSource.consume(async (messageBody) => {

      switch (messageBody.name) {
        case 'roomOver':
          const {scoreMap, roomId} = messageBody.payload

          for (const key in  scoreMap) {
            const value = scoreMap[key]
            const p = this.inPlayers.find(p => p._id === key)
            if (p) p.score = value
          }
          this.finishRoom(roomId)

          this.broadcastToInTournaPlayer({
            name: 'tournament/status',
            payload: {
              _id: this.config._id,
              rooms: this.currentRooms.length, finishedRooms: this.currentRooms.filter(r => r.state === 'finish'),
              rank: this.rank()
            }
          })

          if (this.isTournamentOver()) {
            this.broadcastToInTournaPlayer({
              name: 'tournament/allOver',
              payload: {_id: this.config._id, rank: this.rank()}
            })
            await this.messageSource.close()
            this.broadcaster.close()
            return;
          }

          if (this.isCurrentRoundOver()) {
            this.knockOut()
            await this.startCurrentRound()
          }
          break;
      }
    })

    await this.startCurrentRound()
  }
}
